<?php declare(strict_types=1);
/**
 * @author      xianganyall <xianganyall@gmail.com>
 * @copyright   2023-2025 owner
 **/

namespace Srv\Scripts\Tools;

use Srv\Libs\Boot;
use Srv\Libs\Common\CommFile;
use Srv\Libs\Common\CommJson;
use Srv\Libs\Storage\DataMysql;
use Srv\Libs\Tools\ReflectionClassData;
use Srv\Libs\Tools\ShellPrint;

require_once dirname(__DIR__, 2).'/Libs/Boot.php';
Boot::runConf('');

exit;
// 更新数据库PROTO
$DbAuto             = DataMysql::tag('auto', 'DB_SCRIPT');
$dbName             = $DbAuto->getDbName('', false);
$dbProtoPath        = Boot::getConfPath().'ProtoSrc/DBO/'.ucfirst($dbName).'/';
$protoDBOPackage    = 'DBO.'.ucfirst($dbName);
$protoHeader        = 'syntax = "proto3";'."\n".'package Srv.Apps.Proto.'.$protoDBOPackage.';'."\n";
$protoEnumHeader    = 'syntax = "proto3";'."\n".'package Srv.Apps.Proto.'.$protoDBOPackage.'.Enum;'."\n";
// 枚举类型定义
$protoTypeEnumList  = ['delete' => $protoDBOPackage.'.Enum.DeleteEnum'];
// 删除原来的DBO
CommFile::deleteDir($dbProtoPath);
// 获取数据表的枚举配置
$defEnumList            = $DbAuto->getData('SELECT enum_id, enum_key, enum_desc FROM '.$DbAuto->getTableNameAll('def_enum'));
if(count($defEnumList) > 0){
    $defEnumItemList    = $DbAuto->getData('SELECT enum_id, emrd_val, emrd_name, emrd_desc FROM '.$DbAuto->getTableNameAll('def_enum_record'));
    $defEnumItemsList   = [];
    if(count($defEnumItemList) > 0) foreach ($defEnumItemList as $defEnumItems){
        $enumId         = intval($defEnumItems['enum_id']);
        $emrdVal        = $defEnumItems['emrd_val'];
        $emrdName       = $defEnumItems['emrd_name'];
        $emrdDesc       = $defEnumItems['emrd_desc'];
        if($enumId < 1 || strlen($emrdVal) < 1 || strlen($emrdName) < 1) continue;
        if(!isset($defEnumItemsList[$enumId])) $defEnumItemsList[$enumId] = [];
        $defEnumItemsList[$enumId][$emrdVal] = [$emrdName, $emrdDesc];
    }
    foreach ($defEnumList as $defEnumItem){
        $enumId             = intval($defEnumItem['enum_id']);
        $enumKey            = $defEnumItem['enum_key'];
        $enumDesc           = $defEnumItem['enum_desc'];
        if($enumId < 1 || strlen($enumKey) < 1) continue;
        $enumItem           = $defEnumItemsList[$enumId]??[];
        if(!isset($enumItem[0])) $enumItem[0] = ['UNKNOWN', 'unknown']; // 枚举必须有且第一个是0
        ksort($enumItem);
        $protoEnumName      = '';
        $protoEnumContent   = genEnumProtoContent($enumKey, $enumDesc, $enumItem, $protoEnumName);
        $logText            = str_pad($protoEnumName, 48, ' ', STR_PAD_RIGHT).' // '.$enumDesc."\n";
        $protoTypeEnumList[$enumKey]    = $protoDBOPackage.'.Enum.'.$protoEnumName;
        $protoFilePath                  = $dbProtoPath.'Enum/'.$protoEnumName.'.proto';
        CommFile::writeFileString($protoFilePath, $protoEnumHeader."\n".$protoEnumContent, 'w+');
        ShellPrint::printSuccessString($logText);
    }
}
// 获取数据表数据列表
$tableDataList      = $DbAuto->getTableDataList($dbName);
// 生成数据表
foreach ($tableDataList as $tableItem){
    $tableName          = strtolower($tableItem['name']);
    $tableComment       = preg_replace('/[\n\r]+/', ' ', $tableItem['comment']);
    list($tableNamePrefix, $tableNameOnly) = explode('_', $tableName, 2);
    unset($tableNamePrefix);
    $tableDBOName       = ucfirst(preg_replace_callback('/_(\S)/', static function($match){ return strtoupper($match[1]); }, $tableNameOnly)).'DBO';
    $protoDBOContent    = genDBOProtoContent($dbName, $tableName, $tableDBOName, $tableComment, $tableItem['columns'], $protoTypeEnumList);
    $protoFilePath      = $dbProtoPath.$tableDBOName.'.proto';
    CommFile::writeFileString($protoFilePath, $protoHeader.$protoDBOContent, 'w+');
    $logText            = str_pad($tableDBOName, 48, ' ', STR_PAD_RIGHT).' // '.$tableComment."\n";
    ShellPrint::printSuccessString($logText);
}

// 生成枚举Proto内容
function genEnumProtoContent(string $enumKey, string $enumDesc, array $enumItem, string &$protoEnumName):string
{
    if(strlen($enumKey) < 1 || count($enumItem) < 1) return '';
    $enumValPrefix          = trim(preg_replace_callback('/([A-Z])/', static function($match){ return '_'.strtolower($match[1]); }, $enumKey), '_');
    $enumKey                = preg_replace_callback('/_(\S)/', static function($match){ return strtoupper($match[1]); }, $enumKey);
    $protoEnumName          = ucfirst($enumKey).'Enum';
    $protoEnumContent       = '// '.$enumDesc."\n".'enum '.$protoEnumName."\n".'{'."\n";
    foreach ($enumItem as $enumVal => $enumConf){
        $enumValName        = $enumConf[0];
        $enumValDesc        = $enumConf[1];
        $protoEnumContent   .= str_pad('  '.strtoupper($enumValPrefix.'_'.$enumValName), 48, ' ', STR_PAD_RIGHT);
        $protoEnumContent   .= str_pad('= '.$enumVal.';', 8, ' ', STR_PAD_RIGHT);
        $protoEnumContent   .= '// '.$enumValDesc."\n";
    }
    $protoEnumContent   .= '}';
    return $protoEnumContent;
}
// 生成表Proto内容
function genDBOProtoContent(string $dbName, string $tableName, string $tableDBOName, string $tableComment, array $columns, array $protoTypeEnumList):string
{
    // 推测表字段前缀
    $columnPrefix               = '';
    foreach (array_keys($columns) as $columnKey){
        list($columnPrefixTmp, $columnNameOnly) = explode('_', $columnKey, 2);
        if(in_array($columnNameOnly, ['delete', 'create_time', 'update_time'], true) && strlen($columnPrefixTmp) > 0){
            $columnPrefix       = $columnPrefixTmp;
            break;
        }
    }
    $importList                 = [];
    $tableDBOStr                = '// '.$tableComment.' @'.CommJson::encodeArray([$tableName, $dbName])."\n".'message '.$tableDBOName."\n{\n";
    $tableDBOList               = [];
    foreach ($columns as $columnItem){
        $columnName             = strtolower($columnItem['name']);
        $columnSort             = intval($columnItem['sort']);
        $columnAuto             = strtoupper($columnItem['ext']) === 'AUTO_INCREMENT';
        $columnNameNew          = $columnName;
        if($columnSort === 1){
            list($columnPrefixTmp, $columnNameOnly) = explode('_', $columnName, 2);
            if($columnNameOnly === 'id' && $columnAuto) $columnPrefix = $columnPrefixTmp;
        }else{
            if(strlen($columnPrefix) > 0){
                list($columnNamePrefix, $columnNameOnly) = explode('_', $columnName, 2);
                if($columnPrefix === $columnNamePrefix){
                    $columnNameNew = $columnNameOnly;
                }else{
                    if($columnNameOnly !== 'id' && $columnNameOnly !== 'ids' && preg_match('/^ids?_/', $columnNamePrefix) > 0 && preg_match('/^[a-z]{1,4}$/', $columnNamePrefix) > 0) $columnNameNew = $columnNameOnly;
                }
            }
        }
        $columnNameNew          = preg_replace_callback('/_(\S)/', static function($match){ return strtoupper($match[1]); }, $columnNameNew);
        $columnComment          = preg_replace('/[\n\r]+/', ' ', $columnItem['comment']);
        $enumKey                = '';
        if(preg_match('/\[Enum:\s*(\S+)]/', $columnComment, $match)){
            $enumKey            = $match[1];
            $columnComment      = preg_replace('/\[Enum:\s*(\S+)]/', '', $columnComment);
        }
        $columnTypeStr          = $columnItem['typeStr'];
        $columnType             = strtolower($columnItem['type']);
        $columnTypeLength       = $columnItem['typeLength'];
        $columnTypePrecision    = $columnItem['typePrecision'];
        $columnTypeScale        = $columnItem['typeScale'];
        $columnKeys             = $columnItem['key'];
        array_unshift($columnKeys, $columnName, $columnTypeStr);
        if(isset($protoTypeEnumList[$tableName.'.'.$columnName])){
            $protoType          = $protoTypeEnumList[$tableName.'.'.$columnName];
            $importList[]       = $protoType;
        }else if(strlen($enumKey) > 0 && isset($protoTypeEnumList[$enumKey])){
            $protoType          = $protoTypeEnumList[$enumKey];
            $importList[]       = $protoType;
        }else if(isset($protoTypeEnumList[$columnNameNew])){
            $protoType          = $protoTypeEnumList[$columnNameNew];
            $importList[]       = $protoType;
        }else{
            $protoType          = ReflectionClassData::getProtoTypeByDb($columnTypeStr, $columnType, $columnTypeLength, $columnTypePrecision, $columnTypeScale);
        }
        $tableDBOItem           = str_pad('  '.$protoType, 48, ' ', STR_PAD_RIGHT);
        $tableDBOItem           .= ' '.str_pad($columnNameNew, 40, ' ', STR_PAD_RIGHT);
        $tableDBOItem           .= ' '.str_pad('= '.$columnSort.';', 8, ' ', STR_PAD_RIGHT);
        $tableDBOItem           .= '// '.$columnComment.' @'.CommJson::encodeArray($columnKeys)."\n";
        $tableDBOStr            .= $tableDBOItem;
        $isPk                   = in_array('PRI', $columnKeys, true);
        $isUk                   = in_array('UNI', $columnKeys, true);
        $isIdx                  = in_array('MUL', $columnKeys, true);
        $isNumber               = in_array($columnType, ['tinyint', 'smallint', 'mediumint', 'int', 'bigint', 'decimal', 'float', 'double', 'real', 'bit', 'serial', 'timestamp', 'year'], true);
        $isString               = in_array($columnType, ['varchar', 'char', 'tinytext', 'tinyblob', 'json'], true);
        if($isPk) $tableDBOList['Pk'][] = $tableDBOItem;
        if($isUk) $tableDBOList['Uk'][] = $tableDBOItem;
        if($isPk || $isUk || $isIdx) $tableDBOList['Idx'][] = $tableDBOItem;
        if($isPk || $isUk || $isIdx || $isNumber) $tableDBOList['IdxN'][] = $tableDBOItem;
        if($isPk || $isUk || $isIdx || $isNumber || $isString) $tableDBOList['IdxNC'][] = $tableDBOItem;
    }
    $tableDBOStr                .= "}";
    if(count($tableDBOList) > 0) foreach (['Pk', 'Uk', 'Idx', 'IdxN', 'IdxNC'] as $tableDBOItemIndex){
        if(!isset($tableDBOList[$tableDBOItemIndex])) continue;
        $tableDBOStr            .= "\n\n".'// '.$tableComment.'['.$tableDBOItemIndex.'] @'.CommJson::encodeArray([$tableName, $dbName])."\n".'message '.substr($tableDBOName, 0, -3).$tableDBOItemIndex.substr($tableDBOName, -3)."\n{\n";
        $tableDBOStr            .= implode('', $tableDBOList[$tableDBOItemIndex]);
        $tableDBOStr            .= "}";
    }
    $protoImport                = '';
    if(count($importList) > 0) foreach ($importList as $importValue) $protoImport .= 'import "'.preg_replace('"\."', '/', $importValue).'.proto";'."\n";
    return $protoImport."\n".$tableDBOStr;
}